<cite>
**本文引用的文件**
- [main.go](file://cmd/execgo/main.go)
- [config.go](file://internal/config/config.go)
- [handler.go](file://internal/api/handler.go)
- [task.go](file://internal/models/task.go)
- [executor.go](file://internal/executor/executor.go)
- [http.go](file://internal/executor/http.go)
- [shell.go](file://internal/executor/shell.go)
- [file.go](file://internal/executor/file.go)
- [scheduler.go](file://internal/scheduler/scheduler.go)
- [state.go](file://internal/state/state.go)
- [README.md](file://README.md)
</cite>
目录
简介
本规范文档面向 ExecGo 任务 DSL(领域特定语言),系统性阐述任务对象的结构定义、字段语义与约束、任务类型参数规范、DAG 工作流定义示例、依赖关系与环检测机制、以及任务状态转换与生命周期管理。ExecGo 以纯 Go 标准库实现,提供 HTTP API、DAG 调度、并发执行、可观测性与状态持久化能力,适合作为 AI Agent 的轻量执行内核。
项目结构
ExecGo 采用分层架构:入口程序负责初始化配置、日志、执行器注册、状态管理器、调度器与 HTTP 服务;API 层处理任务提交与查询;调度器负责 DAG 编排与并发控制;执行器模块包含 HTTP/Shell/File 等内置执行器;状态管理器负责内存与磁盘持久化;可观测性模块提供日志、追踪与指标。
graph TB
subgraph "入口与配置"
M["cmd/execgo/main.go"]
C["internal/config/config.go"]
end
subgraph "API 层"
H["internal/api/handler.go"]
end
subgraph "调度器"
S["internal/scheduler/scheduler.go"]
end
subgraph "执行器"
E["internal/executor/executor.go"]
EH["internal/executor/http.go"]
ES["internal/executor/shell.go"]
EF["internal/executor/file.go"]
end
subgraph "状态管理"
ST["internal/state/state.go"]
end
M --> C
M --> E
M --> ST
M --> S
M --> H
H --> S
S --> ST
S --> E
E --> EH
E --> ES
E --> EF
图表来源
- main.go:25-104
- config.go:18-30
- handler.go:39-52
- scheduler.go:34-45
- executor.go:26-67
- http.go:22-75
- shell.go:31-79
- file.go:20-113
- state.go:17-53
章节来源
- main.go:25-104
- README.md:149-177
核心组件
- 任务模型与验证:定义任务对象字段、状态枚举、任务图结构与合法性校验(含依赖引用与环检测)。
- 执行器接口与注册表:统一执行器接口、全局注册表、内置执行器注册。
- 调度器:基于 DAG 的并发调度、就绪队列、信号量并发控制、重试与超时、状态变更与级联传播。
- 状态管理:内存任务映射、原子状态更新、周期性持久化与崩溃恢复。
- API 层:任务提交、查询、删除、健康检查、指标端点。
章节来源
- task.go:21-79
- executor.go:14-67
- scheduler.go:18-97
- state.go:17-134
- handler.go:58-99
架构总览
ExecGo 的任务执行链路如下:客户端通过 HTTP API 提交任务图,API 层进行校验与类型检查后提交给调度器;调度器根据依赖关系与并发限制将任务放入就绪队列,执行器按类型执行任务,完成后更新状态并级联触发下游任务。
sequenceDiagram
participant Client as "客户端"
participant API as "API 层"
participant Sched as "调度器"
participant State as "状态管理"
participant Exec as "执行器"
participant Net as "网络/文件系统"
Client->>API : "POST /tasks"
API->>API : "解析/校验任务图"
API->>Sched : "Submit(graph)"
Sched->>State : "Put(task) + 初始化依赖计数"
Sched->>Sched : "入队无依赖任务"
loop "就绪队列循环"
Sched->>Sched : "获取并发槽"
Sched->>Exec : "Execute(ctx, task)"
Exec->>Net : "发起 HTTP/Shell/文件操作"
Net-->>Exec : "返回结果/错误"
Exec-->>Sched : "返回结果"
Sched->>State : "UpdateStatus(status, result, error)"
alt "成功"
Sched->>Sched : "级联触发下游依赖"
else "失败"
Sched->>State : "标记下游为 skipped 或 failed"
end
end
图表来源
- handler.go:58-99
- scheduler.go:69-97
- scheduler.go:127-190
- scheduler.go:192-222
- state.go:94-108
详细组件分析
任务 DSL 字段定义与约束
- id:任务唯一标识,必填且在任务图中不可重复,不能为空字符串。
- type:任务类型,必填,需与已注册执行器类型一致。
- params:类型相关的参数,以 JSON 形式传入,不同执行器有不同的参数结构。
- depends_on:上游任务 ID 列表,引用必须存在于任务图中且不能自依赖。
- retry:重试次数,非负整数;实际尝试次数为 retry+1,首次尝试不计入重试。
- timeout:毫秒级超时时间,<=0 表示无超时;每次尝试均会创建带超时的 context。
- status:任务状态枚举,支持 pending、running、success、failed、skipped。
- result/error:执行结果与错误信息,由执行器返回并保存。
- created_at/updated_at:任务创建与最近更新时间戳。
任务图校验规则:
- 任务数量 > 0。
- id 唯一且非空。
- type 非空。
- depends_on 引用必须存在且不能自依赖。
- 使用拓扑排序检测环,若存在环则拒绝提交。
章节来源
- task.go:21-34
- task.go:41-79
- task.go:81-121
任务状态转换与生命周期
- 提交阶段:调度器接收任务图,将每个任务状态置为 pending,并记录创建/更新时间。
- 执行阶段:调度器为每个任务获取并发槽,更新状态为 running,调用对应执行器执行。
- 成功:执行器返回成功,调度器更新为 success,指标增加成功计数,级联触发下游依赖。
- 失败:执行器返回错误或超时,调度器按重试策略指数退避多次尝试,最终失败则标记 failed。
- 跳过:当上游依赖失败时,下游任务被标记 skipped,并级联跳过其下游。
- 指标:维护 total、running、succeeded、failed 与按类型统计的指标。
stateDiagram-v2
[*] --> 待执行
待执行 --> 运行中 : "获取并发槽"
运行中 --> 成功 : "执行器返回成功"
运行中 --> 失败 : "执行器返回错误/超时"
运行中 --> 待执行 : "等待重试"
成功 --> 下游触发 : "级联"
失败 --> 结束 : "终止"
下游触发 --> 跳过 : "上游失败"
下游触发 --> 待执行 : "上游成功"
跳过 --> 结束 : "终止"
图表来源
- scheduler.go:127-190
- scheduler.go:192-222
- state.go:94-108
章节来源
- scheduler.go:47-67
- scheduler.go:127-190
- scheduler.go:192-222
任务类型与参数规范
HTTP 执行器
- 类型标识:http
- 参数结构:
- url:必填,目标 URL。
- method:可选,默认 GET。
- headers:可选,请求头键值对。
- body:可选,请求体内容。
- 行为:
- 使用 http.DefaultClient 发起请求。
- 限制响应体大小为 1MB。
- 状态码 >= 400 仍返回结果,但任务标记为失败。
- 返回包含状态码与响应体的 JSON 结果。
章节来源
- http.go:14-20
- http.go:27-75
Shell 执行器
- 类型标识:shell
- 参数结构:
- command:必填,命令名称(支持绝对/相对路径,白名单校验取基础名)。
- args:可选,参数数组。
- dir:可选,执行目录。
- 白名单命令(部分示例):echo、cat、ls、date、whoami、hostname、uname、pwd、curl、wget、ping、dig、grep、awk、sed、head、tail、wc、sort、uniq、find、dir、where、type。
- 行为:
- 仅允许白名单中的命令执行。
- 收集 stdout、stderr 与退出码,返回 JSON 结果。
- 执行失败返回错误信息与结果。
章节来源
- shell.go:14-22
- shell.go:24-29
- shell.go:36-79
File 执行器
- 类型标识:file
- 参数结构:
- action:必填,支持 read、write、append、delete、stat。
- path:必填,文件路径(内部进行清理以防止目录穿越)。
- content:可选,写入内容(仅 write/append 有效)。
- 行为:
- read:返回内容与大小。
- write/append:确保目录存在,写入内容,返回写入字节数。
- delete:删除文件,返回删除成功标志。
- stat:返回文件名、大小、权限、修改时间与是否目录等信息。
- 不支持的 action 返回错误。
章节来源
- file.go:13-18
- file.go:25-52
- file.go:54-113
依赖关系与环检测
- 依赖声明:通过 depends_on 指定上游任务 ID 列表。
- 依赖验证:
- 任务 ID 必须唯一且非空。
- depends_on 引用必须存在于任务图中。
- 不允许自依赖。
- 环检测:使用 Kahn 算法进行拓扑排序,若访问节点数小于任务总数,则判定存在环并拒绝提交。
flowchart TD
Start(["开始"]) --> CheckEmpty["检查任务数量 > 0"]
CheckEmpty --> |否| ErrEmpty["返回错误:任务图为空"]
CheckEmpty --> |是| CheckID["遍历任务:校验 id 非空且唯一"]
CheckID --> CheckType["校验 type 非空"]
CheckType --> CheckDeps["遍历依赖:校验引用存在且不自依赖"]
CheckDeps --> Topo["Kahn 拓扑排序"]
Topo --> Cycle{"访问数 == 任务数?"}
Cycle --> |否| ErrCycle["返回错误:检测到环"]
Cycle --> |是| OK["通过校验"]
图表来源
- task.go:41-79
- task.go:81-121
章节来源
- task.go:41-79
- task.go:81-121
任务定义示例
-
单任务示例(Shell)
- 提交一个 Shell 任务,执行命令并设置重试与超时。
- 示例请求体结构参见:示例请求体:83-97
-
DAG 工作流示例(HTTP → File → File)
- fetch-data:HTTP 执行器拉取数据。
- save-result:File 执行器写入文件,依赖 fetch-data。
- verify:File 执行器读取文件,依赖 save-result。
- 示例请求体结构参见:DAG 示例:101-126
章节来源
- README.md:79-126
依赖关系分析
- API 层依赖调度器与状态管理器,负责任务提交、查询与指标输出。
- 调度器依赖状态管理器与执行器注册表,负责任务编排、并发控制与状态更新。
- 执行器模块提供统一接口与注册表,内置 HTTP/Shell/File 执行器。
- 状态管理器提供内存存储与磁盘持久化,支持崩溃恢复。
- 入口程序负责配置加载、日志初始化、执行器注册、调度器启动与 HTTP 服务启动。
graph LR
API["API 层"] --> SCHED["调度器"]
SCHED --> STATE["状态管理"]
SCHED --> REG["执行器注册表"]
REG --> EXE_HTTP["HTTP 执行器"]
REG --> EXE_SHELL["Shell 执行器"]
REG --> EXE_FILE["File 执行器"]
MAIN["入口程序"] --> API
MAIN --> SCHED
MAIN --> STATE
MAIN --> REG
图表来源
- handler.go:39-52
- scheduler.go:34-45
- executor.go:26-67
- state.go:17-53
- main.go:25-104
章节来源
- handler.go:39-52
- scheduler.go:34-45
- executor.go:26-67
- state.go:17-53
- main.go:25-104
性能考虑
- 并发控制:通过信号量限制最大并发执行数,避免资源争用。
- 超时与重试:每个任务可配置超时与重试次数,执行器在上下文中受控;重试采用指数退避,上限保护。
- 就绪队列:使用带缓冲通道承载就绪任务,避免阻塞。
- 状态持久化:定期将内存状态写入磁盘,保证崩溃后可恢复。
- I/O 限制:HTTP 响应体限制大小,防止过大响应占用内存。
章节来源
- scheduler.go:47-67
- scheduler.go:127-190
- http.go:60-63
- state.go:160-179
故障排查指南
- 提交失败(400):
- 任务图为空或字段缺失:检查 id、type 是否存在。
- 依赖引用不存在或自依赖:确认 depends_on 引用的任务 ID 存在且不指向自身。
- 任务图存在环:修正依赖关系,确保 DAG 无环。
- 未知任务类型:确认类型与已注册执行器一致。
- 执行失败:
- HTTP:检查 URL、方法、超时与网络可达性;关注状态码与响应体。
- Shell:确认命令在白名单中,检查工作目录与参数。
- File:确认路径清理与权限,检查 action 是否支持。
- 状态异常:
- 运行中任务在重启后自动重置为 pending,属预期行为。
- 查看 /metrics 了解任务总数、运行中、成功与失败计数。
- 健康检查:
- /health 返回服务状态、版本与运行时长。
章节来源
- handler.go:58-99
- task.go:41-79
- http.go:27-75
- shell.go:36-79
- file.go:25-52
- state.go:25-53
- state.go:136-158
结论
ExecGo 的任务 DSL 以简洁明确的字段与严格的校验保障了任务图的合法性与可执行性;内置执行器覆盖常见场景,同时通过注册表机制支持扩展;调度器与状态管理器提供了可靠的并发控制、重试与持久化能力。遵循本文规范可快速构建从简单任务到复杂 DAG 的工作流。
附录
任务 DSL 字段一览
- id:字符串,必填,唯一。
- type:字符串,必填,与执行器类型一致。
- params:JSON 对象,类型相关参数。
- depends_on:字符串数组,上游任务 ID 列表。
- retry:整数,非负,重试次数。
- timeout:整数,毫秒,<=0 表示无超时。
- status:字符串,枚举值之一。
- result/error:JSON 对象/字符串,执行结果与错误信息。
- created_at/updated_at:时间戳。
章节来源
- task.go:21-34
执行器类型与参数对照
- HTTP:url、method、headers、body。
- Shell:command、args、dir。
- File:action、path、content。
章节来源
- http.go:14-20
- shell.go:24-29
- file.go:13-18
API 端点概览
- POST /tasks:提交任务图,返回已接受的任务数与任务 ID 列表。
- GET /tasks/{id}:查询单个任务。
- GET /tasks:列出所有任务。
- DELETE /tasks/{id}:删除任务。
- GET /health:健康检查。
- GET /metrics:指标端点。
章节来源
- handler.go:39-52
- handler.go:101-146